﻿using System.Collections.Generic;
using System.Windows.Automation;
using LEET.CommonInterfaces.Automation;
using System;
using Tedds_Tool.Automatables;
using LEET.LEETCommon.WidgetLookup;
using System.Windows;
using LEET.Exceptions.Automation;

namespace LEET.LEETCommon.AutomationAbstractions
{
    public class HierarchyFinder
    {
        #region Attributes

        public static AutomationElement Root;
        public static List<AutomationElement> aes;
        public static List<string> fullpaths;
        public static List<Rect> locations;
        public static List<AEInfo> AECache;
        public static Dictionary<AutomationElement, Dictionary<String, String>> allProperties;

        private static List<string> paths;
        private static List<int> parents;
        private static PropertyCondition editControl = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit);
        //private static readonly object sync = new object();

        #endregion

        /// <summary>
        /// Creates hierarchy cache of all elements and returns element of requested path.
        /// </summary>
        public static AutomationElement GetElement(string key, AutomationElement rootAE)
        {
            SetUIAHierarchy(rootAE);
            return GetElementFromHierarchy(key);
        }

        /// <summary>
        /// Gets cached element from hierarchy cache.
        /// </summary>
        public static AutomationElement GetElementFromHierarchy(IObservableWidget widget)
        {
            try
            {
                if (aes != null && aes.Count > 0)
                    return aes[aes.IndexOf(widget.ae)];
            }
            catch (ArgumentOutOfRangeException)
            {
                
            }
            return null;
        }

        /// <summary>
        /// Gets full path of element from the element.
        /// </summary>
        public static string GetPathFromHierarchy(IObservableWidget widget)
        {
            return GetPathFromHierarchy(widget.ae);
        }

        /// <summary>
        /// Gets full path of element from the element.
        /// </summary>
        public static string GetPathFromHierarchy(AutomationElement ae)
        {
            if (ae == null)
                return "";
            string temppath = "";

            //if can't find path, try find element by hierarchy search then work back to element in current cache.
            if (paths != null && paths.Count > 0)
            {
                IUIPath path = new UIPath();

                try
                {
                    temppath = "(" + ae.Current.AutomationId + "; " + ae.Current.Name + "; " + ae.Current.ClassName + ")";

                    int index = aes.IndexOf(ae);
                    if (index > -1)
                    {
                        return fullpaths[index];
                    }
                }
                catch (ElementNotAvailableException) { }

                List<int> matches = new List<int>();
                for (int i = 0; i < paths.Count; i++)
                    if (paths[i].Equals(temppath))
                        matches.Add(i);
                if (matches.Count == 1)
                    return fullpaths[matches[0]];
                else if(matches.Count == 0)
                {
                    matches = new List<int>();
                    for (int i = 0; i < aes.Count; i++)
                        if (aes[i].Cached.AutomationId.Equals(ae.Current.AutomationId) || aes[i].Cached.Name.Equals(ae.Current.Name))
                            matches.Add(i);
                    if (matches.Count == 1)
                        return fullpaths[matches[0]];
                }
            }
            return temppath;
        }

        /// <summary>
        /// Gets element from full path on replay.
        /// </summary>
        public static AutomationElement GetElementFromHierarchy(string key)
        {
            try
            {
                if (paths != null && paths.Count > 0)
                {
                    if (key == "")
                        return aes[0] != null ? aes[0] : null;

                    List<string> path = new List<string>();
                    for (int i = paths.Count - 1; i > 0; i--)
                    {
                        IUIPath uipath = new UIPath();
                        string temp = paths[i];
                        int index = parents[i];
                        while (index != 0)
                        {
                            temp = temp.Insert(0, paths[index] + ", ");
                            index = parents[index];
                        }

                        List<PathElement> applier = PathApplier.Apply(temp);
                        foreach (PathElement pe in applier)
                            uipath.Add(pe);
                        path.Add(uipath.ToString());
                    }
                    path.Add(paths[0]);

                    //*******************************
                    //List<int> match = new List<int>();
                    //for (int i = 0; i < path.Count; i++)
                    //    if (path[i].Equals(key))
                    //        match.Add(i);
                    //may need to check for control type if more than one match to key
                    //********************************
                    int f = fullpaths.IndexOf(key);
                    return aes[aes.Count - path.IndexOf(key) - 1];
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                try
                {
                    List<PathElement> applier = PathApplier.Apply(key);
                    if (applier[applier.Count - 1].Name != "")
                        return GetElementByName(applier[applier.Count - 1].Name, aes[0]);
                    return GetElementByName(applier[applier.Count - 1].ID, aes[0]);
                }
                catch (InvalidIdentifierException)
                {
                    return GetElementByName(key, aes[0]);
                }
            }
            catch (InvalidIdentifierException)
            {
                return GetElementByName(key, aes[0]);
            }

            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        private static void SetFullPaths()
        {
            
            List<string> path = new List<string>();
            for (int i = paths.Count - 1; i > 0; i--)
            {
                IUIPath uipath = new UIPath();
                string temp = paths[i];
                int index = parents[i];
                while (index != 0)
                {
                    temp = temp.Insert(0, paths[index] + ", ");
                    index = parents[index];
                }

                List<PathElement> applier = new List<PathElement>();
                try
                {
                    applier = PathApplier.Apply(temp);
                }
                catch (InvalidIdentifierException)
                {
                    if (applier.Count == 0)
                        uipath.Add(new PathElement(""));
                }
                    foreach (PathElement pe in applier)
                        uipath.Add(pe);
                    path.Add(uipath.ToString());
            }
            path.Add(paths[0]);

            fullpaths = new List<string>();
            for (int i = 0; i < path.Count; i++)
                fullpaths.Add(path[i]);
            fullpaths.Reverse();
        }

        /// <summary>
        /// Uses root element to set up hierarchy for all elements.
        /// </summary>
        public static void SetUIAHierarchy(AutomationElement rootAE)
        {
            if (rootAE == null)
                return;
            Root = rootAE;

            try
            {
                aes = new List<AutomationElement>();
                paths = new List<string>();
                parents = new List<int>();
                locations = new List<Rect>();
                AECache = new List<AEInfo>();

                AutomationElementCollection aec = rootAE.FindAll(TreeScope.Descendants, System.Windows.Automation.Condition.TrueCondition);
                //System.Threading.Thread.Sleep(50);
                AutomationElementCollection temp = rootAE.FindAll(TreeScope.Descendants, System.Windows.Automation.Condition.TrueCondition);
                while (aec.Count != temp.Count)
                {
                    aec = temp;
                    //System.Threading.Thread.Sleep(50);
                    temp = rootAE.FindAll(TreeScope.Descendants, System.Windows.Automation.Condition.TrueCondition);
                }
                
                //loop to get all elements for loading then continue to cache

                CacheRequest cr = CreateCache();

                AutomationElement current;
                using (cr.Activate())
                {
                    current = rootAE.FindFirst(TreeScope.Subtree, System.Windows.Automation.Condition.TrueCondition);
                }

                if (current == null)
                    return;

                parents.Add(aes.Count - 1);
                aes.Add(current);
                paths.Add(CheckConsistency(current));
                locations.Add(current.Cached.BoundingRectangle);
                AECache.Add(new AEInfo(current, "(" + current.Cached.AutomationId + "; " + current.Cached.Name + "; " + current.Cached.ClassName + ")", current.Cached.BoundingRectangle));
                
                aec = current.CachedChildren;
                int mark = aes.Count - 1;
                foreach (AutomationElement element in aec)
                    AddCachedChilds(element, mark);

                SetFullPaths();
            }
            catch (ElementNotAvailableException)
            {
            }
        }

        private static string CheckConsistency(AutomationElement current)
        {
            string name = current.Cached.Name;
            if (name.Contains("\n"))
            {
                int index = name.IndexOf("\n");
                name = name.Substring(0, index) + @"\n" + @name.Substring(index + 2, name.Length - index - 2);
                //name.Replace("\n", "\\n");
            }
            return "(" + current.Cached.AutomationId + "; " + name + "; " + current.Cached.ClassName + ")";
        }

        /// <summary>
        /// Uses root element of an IObservableWidget object to set up hierarchy for all elements.
        /// </summary>
        /// <param name="rootAE"></param>
        public static void SetUIAHierarchy(IObservableWidget rootAE)
        {
            if (rootAE == null)
                return;
            SetUIAHierarchy(rootAE.ae);
        }

        /// <summary>
        /// Recursive method for adding children to element hierarchy cache.
        /// </summary>
        /// <param name="current"></param>
        /// <param name="parentmarker"></param>
        private static void AddCachedChilds(AutomationElement current, int parentmarker)
        {
            if (current == null)
                return;

            parents.Add(parentmarker);
            aes.Add(current);
            paths.Add(CheckConsistency(current));
            locations.Add(current.Cached.BoundingRectangle);
            AECache.Add(new AEInfo(current, "(" + current.Cached.AutomationId + "; " + current.Cached.Name + "; " + current.Cached.ClassName + ")", current.Cached.BoundingRectangle));
            int mark = aes.Count - 1;
            AutomationElementCollection coll = current.CachedChildren;
            foreach (AutomationElement ae in coll)
                AddCachedChilds(ae, mark);
        }

        /// <summary>
        /// Creates CacheRequest object and adds required properties to cache. The fewer properties, the better performance.
        /// </summary>
        private static CacheRequest CreateCache()
        {
            CacheRequest cr = new CacheRequest();
            cr.AutomationElementMode = AutomationElementMode.Full;
            cr.TreeFilter = Automation.RawViewCondition;
            cr.TreeScope = TreeScope.Subtree;
            cr.Add(AutomationElement.NameProperty);
            cr.Add(AutomationElement.ClassNameProperty);
            cr.Add(AutomationElement.AutomationIdProperty);
            cr.Add(AutomationElement.BoundingRectangleProperty);
            cr.Add(AutomationElement.IsOffscreenProperty);
            return cr;
        }

        #region Standard Searches

        /// <summary>
        /// Secondary search for searching by id only.
        /// </summary>
        private static AutomationElement GetElementById(string identifier, AutomationElement head)
        {
            AutomationElement ae = head.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, identifier));
            return ae;
        }

        /// <summary>
        /// Secondary search for searching by name only.
        /// </summary>
        public static AutomationElement GetElementByName(string identifier, AutomationElement head)
        {
            AutomationElement ae = head.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.NameProperty, identifier));
            if (ae == null)
                return GetElementById(identifier, head);
            return ae;
        }

        /// <summary>
        /// Tiertiary search for search by class name as last resort.
        /// </summary>
        private static AutomationElement GetElementByClass(PathElement path, AutomationElement head)
        {
            AutomationElement ae = null;
            AutomationElementCollection aec = head.FindAll(TreeScope.Descendants, new PropertyCondition(AutomationElement.ClassNameProperty, path.Class));
            foreach (AutomationElement childs in aec)
                if (path.Name == childs.Current.Name && path.ID == childs.Current.AutomationId)
                    return childs;
            return ae;
        }

        #endregion

        /// <summary>
        /// Adds relevant pattern properties for each element in cache for lookup in combobox (for adding verify commands).
        /// </summary>
        public static void AddPatterns(IList<ILEETHackModule> hacks)
        {
            allProperties = new Dictionary<AutomationElement, Dictionary<string,string>>();
            //foreach(AutomationElement ae in aes)
            for(int i = 0; i < aes.Count; i++)
            {
                AutomationElement ae = aes[i];
                Dictionary<String, String> properties = new Dictionary<String, String>();
                bool isImplemented = false;
                object objPattern = null;

                isImplemented = ae.TryGetCurrentPattern(WindowPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    WindowPattern wpattern = objPattern as WindowPattern;
                    properties.Add("WindowPattern.Current.CanMaximize", wpattern.Current.CanMaximize.ToString().Trim());
                    properties.Add("WindowPattern.Current.CanMinimize", wpattern.Current.CanMinimize.ToString().Trim());
                    properties.Add("WindowPattern.Current.IsModal", wpattern.Current.IsModal.ToString().Trim());
                    properties.Add("WindowPattern.Current.IsTopmost", wpattern.Current.IsTopmost.ToString().Trim());
                    properties.Add("WindowPattern.Current.WindowInteractionState", wpattern.Current.WindowInteractionState.ToString().Trim());
                    properties.Add("WindowPattern.Current.WindowVisualState", wpattern.Current.WindowVisualState.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(ValuePattern.Pattern, out objPattern);
                isImplemented = isImplemented || (ae.Current.ControlType == ControlType.Edit);
                if (isImplemented)
                {
                    ValuePattern vpattern = objPattern as ValuePattern;
                    properties.Add("ValuePattern.Current.Value", vpattern.Current.Value.ToString().Trim());
                    properties.Add("ValuePattern.Current.IsReadOnly", vpattern.Current.IsReadOnly.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(TransformPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    TransformPattern tpattern = objPattern as TransformPattern;
                    properties.Add("TransformPattern.Current.CanMove", tpattern.Current.CanMove.ToString().Trim());
                    properties.Add("TransformPattern.Current.CanResize", tpattern.Current.CanResize.ToString().Trim());
                    properties.Add("TransformPattern.Current.CanRotate", tpattern.Current.CanRotate.ToString().Trim());

                    if (tpattern.Current.CanMove)
                        properties.Add(".Current.BoundingRectangle.Location", ae.Current.BoundingRectangle.Location.ToString().Trim());
                    if (tpattern.Current.CanResize)
                        properties.Add(".Current.BoundingRectangle.Size", ae.Current.BoundingRectangle.Size.ToString().Trim());
                    //if(tpattern.Current.CanRotate)
                    //    properties.Add(".Current.BoundingRectangle.Size", ae.Current.BoundingRectangle.Size.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(SelectionPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    SelectionPattern spattern = objPattern as SelectionPattern;
                    properties.Add("SelectionPattern.Current.SelectionProperty", spattern.Current.GetSelection().ToString().Trim());
                    properties.Add("SelectionPattern.Current.IsSelectionRequired", spattern.Current.IsSelectionRequired.ToString().Trim());
                    properties.Add("SelectionPattern.Current.CanSelectMultiple", spattern.Current.CanSelectMultiple.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(SelectionItemPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    SelectionItemPattern sipattern = objPattern as SelectionItemPattern;
                    if (sipattern.Current.SelectionContainer != null)
                        properties.Add("SelectionItemPattern.Current.SelectionContainer", sipattern.Current.SelectionContainer.ToString().Trim());
                    properties.Add("SelectionItemPattern.Current.IsSelected", sipattern.Current.IsSelected.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(ScrollPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    ScrollPattern spattern = objPattern as ScrollPattern;
                    properties.Add("ScrollPattern.Current.HorizontallyScrollable", spattern.Current.HorizontallyScrollable.ToString().Trim());
                    properties.Add("ScrollPattern.Current.HorizontalScrollPercent", spattern.Current.HorizontalScrollPercent.ToString().Trim());
                    properties.Add("ScrollPattern.Current.HorizontalViewSize", spattern.Current.HorizontalViewSize.ToString().Trim());
                    //properties.Add("ScrollPattern.Current.NoScroll", spattern.Current.NoScroll.ToString().Trim());
                    properties.Add("ScrollPattern.Current.VerticallyScrollable", spattern.Current.VerticallyScrollable.ToString().Trim());
                    properties.Add("ScrollPattern.Current.VerticalScrollPercent", spattern.Current.VerticalScrollPercent.ToString().Trim());
                    properties.Add("ScrollPattern.Current.VerticalViewSize", spattern.Current.VerticalViewSize.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(RangeValuePattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    RangeValuePattern rvpattern = objPattern as RangeValuePattern;
                    properties.Add("RangeValuePattern.Current.IsReadOnly", rvpattern.Current.IsReadOnly.ToString().Trim());
                    properties.Add("RangeValuePattern.Current.LargeChange", rvpattern.Current.LargeChange.ToString().Trim());
                    properties.Add("RangeValuePattern.Current.Maximum", rvpattern.Current.Maximum.ToString().Trim());
                    properties.Add("RangeValuePattern.Current.Minimum", rvpattern.Current.Minimum.ToString().Trim());
                    properties.Add("RangeValuePattern.Current.SmallChange", rvpattern.Current.SmallChange.ToString().Trim());
                    properties.Add("RangeValuePattern.Current.Value", rvpattern.Current.Value.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(MultipleViewPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    MultipleViewPattern mvpattern = objPattern as MultipleViewPattern;
                    properties.Add("MultipleViewPattern.Current.CurrentView", mvpattern.Current.CurrentView.ToString().Trim());
                    properties.Add("MultipleViewPattern.Current.SupportedViews", mvpattern.Current.GetSupportedViews().ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(GridPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    GridPattern gpattern = objPattern as GridPattern;
                    properties.Add("GridPattern.Current.ColumnCount", gpattern.Current.ColumnCount.ToString().Trim());
                    properties.Add("GridPattern.Current.RowCount", gpattern.Current.RowCount.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(GridItemPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    GridItemPattern gipattern = objPattern as GridItemPattern;
                    properties.Add("GridItemPattern.Current.Column", gipattern.Current.Column.ToString().Trim());
                    properties.Add("GridItemPattern.Current.ColumnSpan", gipattern.Current.ColumnSpan.ToString().Trim());
                    properties.Add("GridItemPattern.Current.ContainingGrid", gipattern.Current.ContainingGrid.ToString().Trim());
                    properties.Add("GridItemPattern.Current.Row", gipattern.Current.Row.ToString().Trim());
                    properties.Add("GridItemPattern.Current.RowSpan", gipattern.Current.RowSpan.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    ExpandCollapsePattern ecpattern = objPattern as ExpandCollapsePattern;
                    properties.Add("ExpandCollapsePattern.Current.ExpandCollapseState", ecpattern.Current.ExpandCollapseState.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(DockPattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    DockPattern dpattern = objPattern as DockPattern;
                    properties.Add("DockPattern.Current.DockPosition", dpattern.Current.DockPosition.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(TablePattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    TablePattern tpattern = objPattern as TablePattern;
                    properties.Add("TablePattern.Current.ColumnHeaders", tpattern.Current.GetColumnHeaders().ToString().Trim());
                    properties.Add("TablePattern.Current.RowHeaders", tpattern.Current.GetRowHeaders().ToString().Trim());
                    properties.Add("TablePattern.Current.RowOrColumnMajor", tpattern.Current.RowOrColumnMajor.ToString().Trim());
                    isImplemented = false;
                }

                objPattern = null;
                isImplemented = ae.TryGetCurrentPattern(TogglePattern.Pattern, out objPattern);
                if (isImplemented)
                {
                    TogglePattern tpattern = objPattern as TogglePattern;
                    properties.Add("TogglePattern.Current.ToggleState", tpattern.Current.ToggleState.ToString().Trim());
                    isImplemented = false;
                }

                properties.Add(".Current.AutomationId", ae.Current.AutomationId);
                properties.Add(".Current.ClassName", ae.Current.ClassName);
                properties.Add(".Current.FrameworkId", ae.Current.FrameworkId);
                properties.Add(".Current.IsEnabled", ae.Current.IsEnabled.ToString().Trim());
                properties.Add(".Current.Name", ae.Current.Name);

                // hacked properties
                if (hacks != null && ae.Current.AutomationId.EndsWith("_AutomationHack"))
                {
                    ValuePattern pattern = ae.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
                    foreach (ILEETHackModule hack in hacks)
                    {
                        pattern.SetValue("_____" + hack.HackFlag);
                        properties.Add(hack.PropertyName, pattern.Current.Value.ToString().Trim());
                    }
                }

                try
                {
                    allProperties.Add(aes[i], properties);
                }
                catch { }
            }
        }
    }
}
